interface IEventBus {
  events: Map<string, ((...args: unknown[]) => void)[]>;
  on(eventName: string, callback: (...args: unknown[]) => void): void;
  emit(eventName: string, ...args: unknown[]): void;
  off(eventName: string, callback: (...args: unknown[]) => void): void;
  once(eventName: string, callback: (...args: unknown[]) => void): void;
}

class EventBus implements IEventBus {
  events: Map<string, ((...args: unknown[]) => void)[]>;
  constructor() {
    this.events = new Map();
  }
  on(eventName: string, callback: (...args: unknown[]) => void) {
    if (this.events.has(eventName)) {
      const callbacks = this.events.get(eventName);
      callbacks?.push(callback);
    } else {
      this.events.set(eventName, [callback]);
    }
  }
  emit(eventName: string, ...args: unknown[]) {
    const callbacks = this.events.get(eventName);
    callbacks!.forEach((callback) => callback(...args));
  }
  off(eventName: string, callback: (...args: unknown[]) => void) {
    const callbacks = this.events.get(eventName);
    if (callbacks) {
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }
  once(eventName: string, callback: (...args: unknown[]) => void) {
    const onceCallback = (...args: unknown[]) => {
      callback(...args);
      this.off(eventName, onceCallback);
    };
    this.on(eventName, onceCallback);
  }
}
export const eventBus = new EventBus();
